package toolkit

import (
	"encoding/json"
	"gitee.com/hongzhaomin/hzm-common-go/easylog"
	"gitee.com/hongzhaomin/hzm-common-go/strutil"
	"reflect"
	"strings"
)

// ToJson 该方法结构体可以不写json tag，内部会自动帮我们加
func ToJson(obj any) string {
	return ToJson2(obj, true)
}

// ToJson2 该方法会自动识别结构体是否有json的tag，不存在，会自动加上（小驼峰命名）
// omitempty - 表示转json时，是否忽略零值字段
func ToJson2(obj any, omitempty bool) string {
	if obj == nil {
		return strutil.Empty
	}
	rv := reflect.Indirect(reflect.ValueOf(obj))
	rt := rv.Type()
	switch rv.Kind() {
	case reflect.Struct:
		if NotExistTag(rt, strutil.Json) {
			rtNew := addTag4Json(rt, omitempty)
			obj = copyValToNewStruct(rtNew, rv)
		}
	case reflect.Slice, reflect.Array:
		rtEle := rt.Elem()
		if rtEle.Kind() == reflect.Ptr {
			rtEle = rtEle.Elem()
		}
		if rtEle.Kind() != reflect.Struct {
			break
		}
		if NotExistTag(rtEle, strutil.Json) {
			rtEleNew := addTag4Json(rtEle, omitempty)
			newObj := make([]any, 0, rv.Len())
			for i := 0; i < rv.Len(); i++ {
				newEle := copyValToNewStruct(rtEleNew, rv.Index(i))
				newObj = append(newObj, newEle)
			}
			obj = newObj
		}
	default:
		// nothing to do
	}
	return ToJson3(obj)
}

func copyValToNewStruct(rtNew reflect.Type, rvOld reflect.Value) any {
	rtOld := rvOld.Type()
	if rtOld.ConvertibleTo(rtNew) {
		return rvOld.Convert(rtNew).Interface()
	} else {
		//rvNewPtr := reflect.New(rtNew)
		//CopyProperties(rvOld.Interface(), rvNewPtr.Interface())
		rvPtr := reflect.New(rtOld)
		rvPtr.Elem().Set(rvOld)
		rvNewPtr := reflect.NewAt(rtNew, rvPtr.UnsafePointer())
		return rvNewPtr.Interface()
	}
}

// ToJson3 对象转json，仅封装异常处理
func ToJson3(obj any) string {
	jsonObj, err := json.Marshal(obj)
	if err != nil {
		easylog.Error("ToJson3 转json失败，%v", err)
		return strutil.Empty
	}
	return string(jsonObj)
}

// Json2Struct json转对象（泛型）
// 返回泛型结构体指针
func Json2Struct[T comparable](jsonStr string) *T {
	var bs []byte
	if strutil.IsNotBlank(jsonStr) {
		bs = []byte(jsonStr)
	}
	return Json2Struct4Bytes[T](bs)
}

// Json2Struct4Bytes 字节数组转对象（泛型）
// 返回泛型结构体指针
func Json2Struct4Bytes[T comparable](bytes []byte) *T {
	rt := reflect.TypeOf((*T)(nil)).Elem()
	if rt.Kind() != reflect.Struct {
		panic("泛型类型必须为结构体")
	}
	tPtr := new(T)
	if len(bytes) == 0 {
		return tPtr
	}
	err := json.Unmarshal(bytes, tPtr)
	if err != nil {
		easylog.Error("Json2Struct4Bytes -> json转结构体失败，%v", err)
	}
	return tPtr
}

// Json2Map json转map
func Json2Map[K comparable, V any](jsonStr string) map[K]V {
	var m map[K]V
	Json2Obj(jsonStr, &m)
	return m
}

// Json2Obj json转对象
// 通过obj的指针传回
// 所以obj必须为结构体的指针类型
func Json2Obj(jsonStr string, obj any) {
	if obj == nil || strutil.IsBlank(jsonStr) {
		return
	}
	Json2Obj4Bytes([]byte(jsonStr), obj)
}

// Json2Obj4Bytes 字节数组转对象
// 通过obj的指针传回
// 所以obj必须为结构体的指针类型
func Json2Obj4Bytes(bytes []byte, obj any) {
	if obj == nil || bytes == nil {
		return
	}
	rt := reflect.TypeOf(obj)
	if rt.Kind() != reflect.Ptr {
		// 打印日志，必须是指针类型
		return
	}
	err := json.Unmarshal(bytes, obj)
	if err != nil {
		easylog.Error("Json2Obj4Bytes 转json失败，%v", err)
	}
}

// 结构体字段存在自身类型的结构体不要用这个方法，即结构体的下级属性类型不能存在和外层结构体类型相同，同类型指针也不允许
func addTag4Json(rtStruct reflect.Type, omitempty bool) reflect.Type {
	// 前面做了判断
	//if rt.Kind() != reflect.Struct {
	//	panic(errors.New("must be struct kind"))
	//}
	structName := rtStruct.Name()
	// 创建新结构体，添加tag json
	numField := rtStruct.NumField()
	newStructFields := make([]reflect.StructField, 0, numField)
	for i := 0; i < numField; i++ {
		field := rtStruct.Field(i)
		if strutil.IsNotBlank(field.PkgPath) {
			// 字段不可导出
			continue
		}

		rtField := field.Type
		isPtr := false
		if rtField.Kind() == reflect.Ptr {
			rtField = rtField.Elem()
			isPtr = true
		}
		if rtField.Kind() == reflect.Struct {
			if !field.Anonymous {
				field.Tag = addJsonTag(field.Tag, field.Name, omitempty)
			}
			// 和自身结构体类型不相同
			if rtStruct.PkgPath() == rtField.PkgPath() && rtField.Name() == structName {
				easylog.Warn("结构体[%s]中字段[%s]的类型为[%s]类型指针，建议自定义添加tag",
					structName, field.Name, structName)
				goto flag
			}
			field.Type = addTag4Json(rtField, omitempty)
		} else {
			field.Tag = addJsonTag(field.Tag, field.Name, omitempty)
			if rtField.Kind() == reflect.Slice || rtField.Kind() == reflect.Array {
				rtFieldEle := rtField.Elem()
				rtFieldEleIsPrt := false
				if rtFieldEle.Kind() == reflect.Ptr {
					rtFieldEle = rtFieldEle.Elem()
					rtFieldEleIsPrt = true
				}
				if rtFieldEle.Kind() != reflect.Struct {
					goto flag
				}
				// 和自身结构体类型不相同
				if rtStruct.PkgPath() == rtField.Elem().PkgPath() && rtField.Elem().Name() == structName {
					easylog.Warn("结构体[%s]中字段[%s]的切片元素类型为[%s]类型，建议自定义添加tag",
						structName, field.Name, structName)
					goto flag
				}
				rtFieldEleStruct := addTag4Json(rtFieldEle, omitempty)
				if rtFieldEleIsPrt {
					field.Type = reflect.SliceOf(reflect.PointerTo(rtFieldEleStruct))
				} else {
					field.Type = reflect.SliceOf(rtFieldEleStruct)
				}
			}
		}
		if isPtr {
			field.Type = reflect.PointerTo(field.Type)
		}
	flag:
		newStructFields = append(newStructFields, field)
	}
	return reflect.StructOf(newStructFields)
}

func addJsonTag(tag reflect.StructTag, fieldName string, omitempty bool) reflect.StructTag {
	// `json:"xxx"`
	sb := new(strings.Builder)
	sb.WriteString(string(tag))
	fieldNameHump := strutil.FirstLetter2Lower(fieldName)
	if sb.Len() > 0 {
		sb.WriteString(strutil.Space)
	}
	sb.WriteString(strutil.Json)
	sb.WriteString(strutil.Colon)
	sb.WriteString(strutil.Quote)
	sb.WriteString(fieldNameHump)
	if omitempty {
		sb.WriteString(strutil.Comma)
		sb.WriteString(strutil.Omitempty)
	}
	sb.WriteString(strutil.Quote)
	return reflect.StructTag(sb.String())
}
